package com.smartleanx.module.hrm.service.payroll;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.smartleanx.framework.common.enums.BizTypeEnum;
import com.smartleanx.framework.common.exception.ServiceException;
import com.smartleanx.framework.common.pojo.PageParam;
import com.smartleanx.framework.common.pojo.PageResult;
import com.smartleanx.framework.common.util.collection.CollectionUtils;
import com.smartleanx.framework.common.util.object.BeanUtils;
import com.smartleanx.framework.customfield.core.service.CustomFieldDataService;
import com.smartleanx.framework.customfield.core.service.bo.CustomFieldDataSaveBO;
import com.smartleanx.framework.customfield.core.service.bo.inner.CustomFieldDataBaseBO;
import com.smartleanx.framework.datapermission.core.util.DataPermissionUtils;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollDetailPageReqVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollImportRespVO;
import com.smartleanx.module.hrm.controller.admin.payroll.vo.PayrollPageReqVO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDO;
import com.smartleanx.module.hrm.dal.dataobject.payroll.PayrollDetailDO;
import com.smartleanx.module.hrm.dal.dataobject.payrollapproverecord.PayrollApproveRecordDO;
import com.smartleanx.module.hrm.dal.dataobject.payrollapproverecord.PayrollApproveRecordDetailDO;
import com.smartleanx.module.hrm.dal.mysql.payroll.PayrollDetailMapper;
import com.smartleanx.module.hrm.dal.mysql.payroll.PayrollMapper;
import com.smartleanx.module.hrm.dal.mysql.payrollapproverecord.PayrollApproveRecordDetailMapper;
import com.smartleanx.module.hrm.dal.mysql.payrollapproverecord.PayrollApproveRecordMapper;
import com.smartleanx.module.hrm.eums.payroll.PayrollStateEnum;
import com.smartleanx.module.hrm.framework.operate.core.annotations.OperationLog;
import com.smartleanx.module.hrm.framework.operate.core.enums.DocTypeEnum;
import com.smartleanx.module.hrm.framework.operate.core.enums.OperationTypeEnum;
import com.smartleanx.module.hrm.framework.operate.core.util.OperationLogUtils;
import com.smartleanx.module.hrm.intergration.CustomFieldServiceProvider;
import com.smartleanx.module.hrm.service.payroll.bo.PayrollDetailFieldSaveBO;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldReqDTO;
import com.smartleanx.module.system.api.customfield.dto.CustomFieldRespDTO;
import com.smartleanx.module.system.enums.customfield.CustomFieldLabelEnum;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;

import static com.smartleanx.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.smartleanx.module.hrm.enums.ErrorCodeConstants.*;

/**
 * 薪资月份 Service 实现类
 *
 * @author zhangchp
 */
@Service
@Validated
public class PayrollServiceImpl implements PayrollService {

    @Resource
    private PayrollMapper payrollMapper;

    @Resource
    private PayrollDetailMapper payrollDetailMapper;

    @Resource
    private PayrollDetailService payrollDetailService;

    @Resource
    private PayrollApproveRecordMapper payrollApproveRecordMapper;

    @Resource
    private PayrollApproveRecordDetailMapper payrollApproveRecordDetailMapper;

    @Resource
    private CustomFieldServiceProvider customFieldServiceProvider;

    @Resource
    private CustomFieldDataService customFieldDataService;

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public void archive(Long id) {
        // 校验
        validatePayrollExists(id);
        validateNotArchive(id);
        // 更新
        PayrollDetailPageReqVO reqVO = PayrollDetailPageReqVO.builder().payrollId(id).archived(false).build();
        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<PayrollDetailDO> detailList = payrollDetailService.getDetailPage(reqVO).getList();
        if(CollUtil.isNotEmpty(detailList)){
            PayrollDO payroll = payrollMapper.selectById(id);
            PayrollDO updateObj = PayrollDO.builder().id(id).people(NumberUtil.nullToZero(payroll.getPeople()) + detailList.size())
                    .salary(NumberUtil.nullToZero(payroll.getSalary()).add(CollectionUtils.getSumValue(detailList,
                            detail -> BigDecimal.valueOf(Double.parseDouble(detail.getEffectiveSalary())), BigDecimal::add)))
                    .state(PayrollStateEnum.ARCHIVING.getValue()).build();
            payrollDetailService.archive(id);
            DataPermissionUtils.executeIgnore(() -> {
                if(payrollDetailService.getDetailPage(reqVO).getTotal() == 0) {
                    updateObj.setState(PayrollStateEnum.ARCHIVE.getValue());
                }
            });
            payrollMapper.updateById(updateObj);
            if(PayrollStateEnum.ARCHIVE.getValue().equals(updateObj.getState())){
                customFieldServiceProvider.archiveField(BizTypeEnum.PAYROLL.getValue(), id);
            }
        }
    }

    private void validatePayrollExists(Long id) {
        if (payrollMapper.selectById(id) == null) {
            throw exception(PAYROLL_NOT_EXISTS);
        }
    }

/*    private void validateAllowPayStub(Long id) {
        PayrollDO payroll = payrollMapper.selectById(id);
        if(Objects.isNull(payroll)){
            throw exception(PAYROLL_NOT_EXISTS);
        }
        if(!PayrollStateEnum.INITIAL.getValue().equals(payroll.getState())){
            throw exception(PAYROLL_STATE_PAY_STUB_ERROR);
        }
    }*/

    @Override
    public void validateNotArchive(Long id) {
        PayrollDO payroll = payrollMapper.selectById(id);
        if(Objects.isNull(payroll)){
            throw exception(PAYROLL_NOT_EXISTS);
        }
        if(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())){
            throw exception(PAYROLL_ARCHIVED);
        }
    }

    @Override
    public PayrollDO getPayroll(Long id) {
        return payrollMapper.selectById(id);
    }

    @Override
    public PayrollDO getPayrollByState(Collection<Integer> states) {
        return payrollMapper.selectByState(states);
    }

    @Override
    public PageResult<PayrollDO> getPayrollPage(PayrollPageReqVO pageReqVO) {
        return payrollMapper.selectPage(pageReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    @OperationLog(docType = DocTypeEnum.PAYROLL, operateType = OperationTypeEnum.PAYROLL_IMPORT)
    public PayrollImportRespVO importPayrollList(List<Map<String, String>> excelDataList, Integer year, Integer month) {
        PayrollDO payroll = payrollMapper.selectByYearAndMonthAndState(year, month,
                CollUtil.newArrayList(PayrollStateEnum.INITIAL.getValue(), PayrollStateEnum.ARCHIVING.getValue()));
        if(Objects.isNull(payroll)){
            payroll = PayrollDO.builder().year(year).month(month).state(PayrollStateEnum.INITIAL.getValue()).build();
            payrollMapper.insert(payroll);
        }
        if(PayrollStateEnum.ARCHIVE.getValue().equals(payroll.getState())){
            throw exception(PAYROLL_IMPORT_STATE_ERROR);
        }
        Long id = payroll.getId();
        List<CustomFieldDataBaseBO> fieldDataList = new ArrayList<>();
        List<Map<String, PayrollDetailFieldSaveBO>> repeatList = new ArrayList<>();
        List<String> successList = new ArrayList<>(), failList = new ArrayList<>();
        Map<String, CustomFieldRespDTO> nameMap = CollectionUtils.convertMap(customFieldServiceProvider.getFieldList(
                CustomFieldReqDTO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .labels(CollUtil.newArrayList(CustomFieldLabelEnum.PAYROLL_EMPLOYEE_INFO.getValue(),
                                CustomFieldLabelEnum.PAYROLL_DETAIL_INFO.getValue())).build()), CustomFieldRespDTO::getName);
        excelDataList.forEach(map -> {
            List<PayrollDetailFieldSaveBO> data = new ArrayList<>();
            map.forEach((key, val) -> {
                if(nameMap.containsKey(key)){
                    CustomFieldRespDTO field = nameMap.get(key);
                    PayrollDetailFieldSaveBO saveBO = BeanUtils.toBean(field, PayrollDetailFieldSaveBO.class);
                    saveBO.setFieldId(field.getId());
                    saveBO.setColName(field.getColName());
                    saveBO.setFieldName(field.getName());
                    saveBO.setFieldValue(val);
                    saveBO.setRecordId(0L);
                    data.add(saveBO);
                }else{
                    throw exception(PAYROLL_IMPORT_FIELD_ERROR);
                }
            });
            try {
                PayrollDetailDO detail = payrollDetailService.createDetail(data, id);
                fieldDataList.addAll(CollectionUtils.convertList(data, obj -> {
                    CustomFieldDataBaseBO saveBO = BeanUtils.toBean(obj, CustomFieldDataBaseBO.class);
                    saveBO.setRecordId(detail.getId());
                    return saveBO;
                }));
                successList.add(detail.getEmployeeNo());
            } catch (ServiceException e) {
                if(PAYROLL_DETAIL_CREATED.getCode().equals(e.getCode())){
                    repeatList.add(CollectionUtils.convertMap(data, PayrollDetailFieldSaveBO::getColName));
                }else if(PAYROLL_DETAIL_EXISTS.getCode().equals(e.getCode())){
                    failList.add(e.getMessage());
                }else{
                    throw e;
                }
            }
        });
        if(CollUtil.isNotEmpty(failList)){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            OperationLogUtils.setOperationInfo(payroll.getId(), Collections.singletonMap("result", "失败"));
            return PayrollImportRespVO.builder().failureRecords(failList).build();
        }else if(CollUtil.isNotEmpty(fieldDataList)){
            customFieldDataService.crudData(CustomFieldDataSaveBO.builder().datas(fieldDataList).bizType(BizTypeEnum.PAYROLL.getValue())
                    .labels(CollectionUtils.convertSet(fieldDataList, CustomFieldDataBaseBO::getLabel))
                    .labelGroups(CollectionUtils.convertSet(fieldDataList, CustomFieldDataBaseBO::getLabelGroup))
                    .recordIds(CollectionUtils.convertSet(fieldDataList, CustomFieldDataBaseBO::getRecordId)).build());
        }
        // 日志相关
        OperationLogUtils.setOperationInfo(payroll.getId(), Collections.singletonMap("result", "成功"));
        return PayrollImportRespVO.builder().id(payroll.getId()).createRecords(successList).repeatRecords(repeatList).build();
    }

    @Override
    @Transactional
    public void deletePayroll(Long id) {
        PayrollDO payrollDO = payrollMapper.selectById(id);
        if(payrollDO != null){
            List<PayrollDetailDO> detailList = payrollDetailMapper.selectListByPayrollId(id);;
            for(PayrollDetailDO detail:detailList){
                payrollDetailMapper.deleteById(detail.getId());
                customFieldDataService.crudData(CustomFieldDataSaveBO.builder().bizType(BizTypeEnum.PAYROLL.getValue())
                        .labels(Collections.emptyList()).labelGroups(Collections.emptyList()).recordIds(Collections.singletonList(detail.getId())).build());
            }
            List<PayrollApproveRecordDO> recordList = payrollApproveRecordMapper.selectList(new LambdaQueryWrapper<PayrollApproveRecordDO>() {{
                eq(PayrollApproveRecordDO::getPayrollId, id);
            }});
            for(PayrollApproveRecordDO record:recordList){
                List<PayrollApproveRecordDetailDO> recordDetailList = payrollApproveRecordDetailMapper.selectByPayrollApproveRecordId(record.getId());
                for(PayrollApproveRecordDetailDO detail:recordDetailList){
                    payrollApproveRecordDetailMapper.deleteById(detail.getId());
                }
                payrollApproveRecordMapper.deleteById(record.getId());
            }
        }
    }

}